package com.cordys.opensource.crom;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;

import com.cordys.cpc.bsf.busobject.BusObject;
import com.cordys.cpc.bsf.busobject.BusObjectIterator;
import com.cordys.cpc.bsf.busobject.BusObjectManager;
import com.cordys.cpc.bsf.busobject.BusObjectUID;
import com.cordys.cpc.bsf.event.AfterAbortObjectEvent;
import com.cordys.cpc.bsf.event.AfterCommitObjectEvent;
import com.cordys.cpc.bsf.event.BeforeAbortObjectEvent;
import com.cordys.cpc.bsf.event.BeforeCommitObjectEvent;

public class TransactionContext
{
	private HashMap<Object, DomainObjectPersistence> persistenceObjects = new HashMap<Object, DomainObjectPersistence>();
	private HashMap<BusObject, BusObjectWrapper> domainObjects = new HashMap<BusObject, BusObjectWrapper>();
	
	/**
	 * Contains objects under modification in the current WS-AppServer transaction.
	 */
	private static HashMap<BusObjectManager, TransactionContext> transactions = new HashMap<BusObjectManager, TransactionContext>();
	
	static BusObjectWrapper getDomainObjectWrapper(BusObject domainObject)
	{
		BusObjectManager bom = domainObject.getObjectManager();
		TransactionContext transactionContext = transactions.get(bom);
		if (transactionContext==null)
		{
			transactionContext = new TransactionContext();
			transactions.put(bom, transactionContext);
		}
		
		BusObjectWrapper dow = transactionContext.domainObjects.get(domainObject);
		if (dow==null)
		{
			dow = new BusObjectWrapper(domainObject, transactionContext);
			transactionContext.domainObjects.put(domainObject, dow);
		}
		return dow;
	}
	
	/**
	 * Returns the UID of the domain object, or the domain object itself if none was available.
	 * @return
	 */
	private Object getKey(BusObject domainObject)
	{
		Object key = BusObjectHelper.getUID(domainObject);
		if (key==null) // In case an object is inserted
		{
			key = domainObject;
		}
		else // check whether the object is already registered earlier without having a UID. In that case upgrade the administration
		{
			DomainObjectPersistence doi = persistenceObjects.get(domainObject);
			if (doi!=null) // we found an earlier registration of the object (dating from a time that we did not have a proper UID)
			{
				persistenceObjects.put(key, doi); // store it under the new key
				persistenceObjects.remove(domainObject);// and remove the old key
			}
		}
		return key;
	}

	/**
	 * Gives the related database records for the domain object.
	 * Multiple different domain objects can point to the same database objects.
	 * The UID of the domain object is used to identify appropriate database objects.
	 * @return
	 */
	DomainObjectPersistence getPersistenceMapping(BusObject domainObject)
	{
		Object key = getKey(domainObject);
		DomainObjectPersistence persistence = persistenceObjects.get(key);
		if (persistence==null)
		{
			persistence = new DomainObjectPersistence();
			persistenceObjects.put(key, persistence);
		}
		return persistence;
	}
	
	/**
	 * Takes a BusObject and checks if it is already in the current
	 * transaction. This is typically done for query results. 
	 * This call retrieves the transaction context of the BusObject,
	 * and checks whether an object with the same BusObjectUID is already available in the current transaction context.
	 * If so, the object from the transaction will be returned. If not, the original busobject will be returned
	 * @param queriedObject A busobject to be checked. Typically the return value of QueryObject.getObject()
	 * @return The object, possibly replaced with the one that is already modified in the current transaction.
	 */
	public static BusObject replaceCachedObject(BusObject queriedObject)
	{
		BusObjectUID queriedUID = BusObjectHelper.getUID(queriedObject);
		TransactionContext tc = transactions.get(queriedObject.getObjectManager());

		if (tc!=null && queriedUID!=null)
		{
			Vector<BusObject> cachedObjects = tc.getBusObjects();
			for (BusObject cachedObject : cachedObjects)
			{
				BusObjectUID cachedUID = BusObjectHelper.getUID(cachedObject);
				if (queriedUID.equals(cachedUID))
				{
					return cachedObject;
				}
			}
		}
		return queriedObject;
	}
	
	/**
	 * Takes a set of BusObjects and checks for each of them if they are already in the current
	 * transaction. This is typically done for query results. 
	 * This call iterates the BusObjectIterator, retrieves the transaction context,
	 * and checks whether an object with the same BusObjectUID is already available in the current transaction context.
	 * If so, the object from the transaction will be added to the result. If not, the object from the iterator
	 * will be added to the result.
	 * Internally invoked replaceCachedObject
	 * @param queriedObjects An iterator with the objects to be checked. Typically the return value of QueryObject.getObjects()
	 * @return An iterator with the same objects, possibly replaced with those that are already modified in the current transaction.
	 */
	public static Iterator<BusObject> replaceCachedObjects(BusObjectIterator<BusObject> queriedObjects)
	{
		Vector<BusObject> result = new Vector<BusObject>();
		while (queriedObjects.hasMoreElements())
		{
			result.add(replaceCachedObject(queriedObjects.nextElement()));
		}
		return result.iterator();
	}
	
	private Vector<BusObject> getBusObjects()
	{
		Vector<BusObject> v = new Vector<BusObject>();
		for (BusObjectWrapper dow : domainObjects.values())
		{
			v.add(dow.getDomainObject());
			DomainObjectPersistence dop = dow.getPersistenceMapping(); 
			v.add(dop.getDatabaseObject());
			v.add(dop.getNewRevision());
			v.add(dop.getActiveRevision());
		}
		return v;
	}

	void afterCommitTransaction()
	{
		for (BusObjectWrapper dow : domainObjects.values())
		{
			dow.onAfterCommit();
		}
	}

	void afterAbortTransaction()
	{
		for (BusObjectWrapper dow : domainObjects.values())
		{
			dow.onAfterAbort();
		}
	}

	void beforeCommitTransaction()
	{
		for (BusObjectWrapper dow : domainObjects.values())
		{
			dow.onBeforeCommit();
		}
	}

	void beforeAbortTransaction()
	{
		for (BusObjectWrapper dow : domainObjects.values())
		{
			dow.onBeforeAbort();
		}
	}
	
	static void afterCommit(AfterCommitObjectEvent event)
	{
		BusObject domainObject = event.getObject();
		TransactionContext transactionContext = transactions.remove(domainObject.getObjectManager());
		if (transactionContext!=null)
		{
			transactionContext.afterCommitTransaction();
		}
	}
	
	static void afterAbort(AfterAbortObjectEvent event)
	{
		BusObject domainObject = event.getObject();
		TransactionContext transactionContext = transactions.remove(domainObject.getObjectManager());
		if (transactionContext!=null)
		{
			transactionContext.afterAbortTransaction();
		}
	}

	static void beforeCommit(BeforeCommitObjectEvent event)
	{
		BusObject domainObject = event.getObject();
		TransactionContext transactionContext = transactions.get(domainObject.getObjectManager());
		if (transactionContext!=null)
		{
			transactionContext.beforeCommitTransaction();
		}
	}
	
	static void beforeAbort(BeforeAbortObjectEvent event)
	{
		BusObject domainObject = event.getObject();
		TransactionContext transactionContext = transactions.get(domainObject.getObjectManager());
		if (transactionContext!=null)
		{
			transactionContext.beforeAbortTransaction();
		}
	}
}
